class MyPromise {
  static PENDING = 'pending'
  static FULFILLED = 'fufilled'
  static REJECTED = 'rejected'
  constructor (func) {
    this.PromiseState = MyPromise.PENDING
    this.PromiseResult = null
    this.onFulfilledCbs = []
    this.onRejectedCbs = []
    try {
      func(this.resolve, this.reject)
    } catch (error) {
      this.reject(error)
    }
  }
  resolve = result => {
    if (this.PromiseState === MyPromise.PENDING) {
      setTimeout(() => {
        this.PromiseState = MyPromise.FULFILLED
        this.PromiseResult = result
        this.onFulfilledCbs.forEach(cb => {
          cb(result)
        })
      })
    }
  }
  reject = reason => {
    if (this.PromiseState === MyPromise.PENDING) {
      setTimeout(() => {
        this.PromiseState = MyPromise.REJECTED
        this.PromiseResult = reason
        this.onRejectedCbs.forEach(cb => {
          cb(reason)
        })
      })
    }
  }
  then = (onFulfilled, onRejected) => {
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : result => result
    onRejected = typeof onRejected === 'function' ? onRejected : reason => {
      throw reason
    }
    const promise = new MyPromise((resolve, reject) => {
      if (this.PromiseState === MyPromise.PENDING) {
        this.onFulfilledCbs.push(() => {
          setTimeout(() => {
            try {
              resolvePromise(promise, onFulfilled(this.PromiseResult), resolve, reject)
            } catch (error) {
              reject(error)
            }
          })
        })
        this.onRejectedCbs.push(() => {
          setTimeout(() => {
            try {
              resolvePromise(promise, onRejected(this.PromiseResult), resolve, reject)
            } catch (error) {
              reject(error)
            }
          })
        })
      } else if (this.PromiseState === MyPromise.FULFILLED) {
        setTimeout(() => {
          try {
            resolvePromise(promise, onFulfilled(this.PromiseResult), resolve, reject)
          } catch (error) {
            reject(error)
          }
        })
      } else if (this.PromiseState === MyPromise.REJECTED) {
        setTimeout(() => {
          try {
            resolvePromise(promise, onRejected(this.PromiseResult), resolve, reject)
          } catch (error) {
            reject(error)
          }
        })
      }
    })
    return promise
  }
}

function resolvePromise (promise, x, resolve, reject) {
  if (promise === x) {
    return reject(new TypeError('Chaining cycle detected for promise'))
  }
  if (x instanceof MyPromise) {
    if (x.PromiseState === MyPromise.PENDING) {
      x.then(res => {
        resolvePromise(promise, res, resolve, reject)
      }, reject)
    } else if (x.PromiseState === MyPromise.FULFILLED) {
      resolve(x.PromiseResult)
    } else if (x.PromiseState === MyPromise.REJECTED) {
      reject(x.PromiseResult)
    }
  } else if (x !== null && ((typeof x === 'object') || (typeof x === 'function'))) {
    let then = null
    try {
      then = x.then
    } catch (error) {
      return reject(error)
    }
    if (typeof then === 'function') {
      let called = false;
      try {
        then.call(
          x,
          res => {
            if (called) return
            called = true
            resolvePromise(promise, res, resolve, reject)
          },
          r => {
            if (called) return
            called = true
            reject(r)
          }
        )
      } catch (error) {
        if (called) return
        called = true
        reject(error)
      }
    } else {
      resolve(x)
    }
  } else {
    return resolve(x)
  }
}

MyPromise.deferred = () => {
  let result = {}
  result.promise = new MyPromise((resolve, reject) => {
    result.resolve = resolve
    result.reject = reject
  })
  return result
}

module.exports = MyPromise
